Skip to main content

Type Declaration

Ambient Declarations

In TypeScript, ambient declarations are a way to tell the compiler about the existence and shape of code that’s defined outside TypeScript — like in JavaScript libraries, global variables, or external APIs.

They live inside .d.ts files (declaration files), and do not generate JavaScript output. They’re purely for type checking and editor IntelliSense.

Think of them as “type definitions without implementation”.

Why Do We Need .d.ts Files?

  • TypeScript needs type information, but plain JavaScript libraries don’t provide it.
  • .d.ts files bridge the gap: they describe the types of values, functions, and classes that come from elsewhere.
  • Popular JS libraries (React, Express, Lodash, etc.) ship with .d.ts files, or you can install them via DefinitelyTyped (npm install @types/react).

Syntax of Ambient Declarations

They often use the declare keyword. Examples:

  • declare var → for global variables
  • declare function → for global functions
  • declare class → for global classes
  • declare module → for modules
  • declare namespace → for grouping related declarations

Declaring a Global Variable

Suppose a JavaScript file adds a global variable:

// script.js
window.myGlobal = "Hello world!";

In TypeScript, we can declare it:

// globals.d.ts
declare var myGlobal: string;

Declaring a Module

If you’re using a JavaScript library that doesn’t have types, you can declare a module:

// lodash.d.ts
declare module "lodash" {
export function chunk<T>(array: T[], size?: number): T[][];
}

Now in your TypeScript code:

import { chunk } from "lodash";

let result = chunk([1, 2, 3, 4], 2); // [[1, 2], [3, 4]]

Even though Lodash is written in JS, you get type safety + autocomplete.

Declaring a Namespace

// myLib.d.ts
declare namespace MyLib {
function greet(name: string): void;
let version: string;
}

Usage:

MyLib.greet("Alice");
console.log(MyLib.version);

Merging with Existing Types

You can augment existing modules:

// express-custom.d.ts
import "express";

declare module "express" {
interface Request {
user?: { id: number; name: string };
}
}

Now in Express code:

app.use((req, res, next) => {
req.user = { id: 1, name: "Alice" }; // ✅ recognized
next();
});

Key Rules of .d.ts Files

  1. They don’t generate JS output — only types.
  2. They must use declare for globals, modules, etc.
  3. You can place them:
  • in your project (src/types/ or @types/)
  • or install from DefinitelyTyped (@types/* packages).
  1. TypeScript automatically picks up .d.ts files if they’re in your project or node_modules/@types.

DefinitelyTyped

  • DefinitelyTyped is a huge open-source repository that contains type declaration files (.d.ts) for popular JavaScript libraries.
  • Since many JS libraries don’t ship with TypeScript support, the community maintains these declaration files separately.
  • You can install them via npm as @types/<library-name>.

Example:

  • React itself is plain JS (historically).
  • To use React in TypeScript, you install:
    npm install react react-dom
    npm install --save-dev @types/react @types/react-dom
  • Now your TS project knows the types of React components, hooks, etc.

How It Works

  1. Install a JS library (npm install lodash).
  2. Install its type declarations (npm install --save-dev @types/lodash).
  3. TypeScript automatically finds the .d.ts file in node_modules/@types.
  4. You get type safety + IntelliSense without writing your own declarations.

Using Lodash with @types/lodash

Without types, TypeScript doesn’t know what _.chunk does:

import _ from "lodash";

let result = _.chunk([1, 2, 3, 4], 2);
console.log(result);

If @types/lodash is installed:

  • TypeScript knows chunk<T>(array: T[], size?: number): T[][].
  • result is correctly inferred as number[][].
  • If you misuse it:
_.chunk(123, 2); // ❌ Error: Argument of type 'number' is not assignable to parameter of type 'any[]'

TypeScript catches the error at compile time.

Using Express with @types/express

npm install express
npm install --save-dev @types/express

Now in TypeScript:

import express, { Request, Response } from "express";

const app = express();

app.get("/", (req: Request, res: Response) => {
res.send("Hello World");
});
  • Request and Response come from @types/express.
  • You get full IntelliSense for Express APIs.

Declaring types for external libraries

This is needed when you use a JavaScript library with no built-in types and no @types/... package. In such cases, you must declare the types yourself so TypeScript knows how to handle the library.

Why Do We Need to Declare Types?

  • TypeScript only understands types it has definitions for.
  • Many modern libraries include their own .d.ts files ✅
  • Others rely on DefinitelyTyped (@types/...) ✅
  • But sometimes: ❌ no built-in types, ❌ no @types available → you must declare types manually.

Ways to Declare Types for External Libraries

  • Quick and dirty: declare the module as any.
  • Create a .d.ts file with basic type definitions.
  • Write a full declaration file (index.d.ts) with detailed typings.
  • Publish to DefinitelyTyped if it’s useful for the community.

Declaring a Module as any

Suppose you import a JS library called cool-lib, but TS has no idea about it:

import cool from "cool-lib"; // ❌ Error: Cannot find module 'cool-lib'

Fix with a quick .d.ts:

// cool-lib.d.ts
declare module "cool-lib";

Now TypeScript stops complaining, but everything is any:

cool.doSomething(); // no IntelliSense, no type safety

Useful as a temporary fix, but not recommended long term.

Declaring a Module with Specific Functions

Suppose cool-lib exports a function greet(name: string): string.

// cool-lib.d.ts
declare module "cool-lib" {
export function greet(name: string): string;
}

Now in your TS code:

import { greet } from "cool-lib";

console.log(greet("Alice")); // ✅ typed as string

TypeScript knows greet takes a string and returns a string.

If you misuse it:

greet(123); // ❌ Error: Argument of type 'number' is not assignable to parameter of type 'string'